/*
 * Example usage:
 * # frida -U -f com.target.app -l raptor_frida_android_trace.js --no-pause
 *
 * Get the latest version at:
 * https://github.com/0xdea/frida-scripts/
 */

// generic trace
function trace(pattern)
{
	var type = (pattern.toString().indexOf("!") === -1) ? "java" : "module";

	if (type === "module") {
		console.log("---------------module---------------")

		// trace Module
		var res = new ApiResolver("module");
		var matches = res.enumerateMatchesSync(pattern);
		var targets = uniqBy(matches, JSON.stringify);
		targets.forEach(function(target) {
			try{
				traceModule(target.address, target.name);
			}
			catch(err){}
		});

	} else if (type === "java") {

		console.log("---------------java---------------")

		// trace Java Class
		var found = false;
		Java.enumerateLoadedClasses({
			onMatch: function(aClass) {
				if (aClass.match(pattern)) {
					found = true;
					console.log("\n------found is true------")

					console.log("before:"+aClass)
					//var className = aClass.match(/[L](.*);/)[1].replace(/\//g, ".");
					var className = aClass.match(/[L]?(.*);?/)[1].replace(/\//g, ".");
					console.log("after:"+className)
					traceClass(className);


				}
			},
			onComplete: function() {}
		});

		// trace Java Method
		if (!found) {
			try {
				traceMethod(pattern);
			}
			catch(err) { // catch non existing classes/methods
				console.error(err);
			}
		}
	}
}

// find and trace all methods declared in a Java Class
function traceClass(targetClass)
{

	console.log("[*]entering traceClass")

	var hook = Java.use(targetClass);
	var methods = hook.class.getDeclaredMethods();
	hook.$dispose();

	console.log("[*]entering pasedMethods")

	var parsedMethods = [];
	methods.forEach(function(method) {
		try{
			parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
		}
		catch(err){}
	});

	console.log("[*]entering traceMethods")


	var targets = uniqBy(parsedMethods, JSON.stringify);
	targets.forEach(function(targetMethod) {
		try{
			traceMethod(targetClass + "." + targetMethod);
		}
		catch(err){}
	});
}

// trace a specific Java Method
function traceMethod(targetClassMethod)
{
	var delim = targetClassMethod.lastIndexOf(".");
	if (delim === -1) return;

	var targetClass = targetClassMethod.slice(0, delim)
	var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length)

	var hook = Java.use(targetClass);
	var overloadCount = hook[targetMethod].overloads.length;

	console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");

	for (var i = 0; i < overloadCount; i++) {

		hook[targetMethod].overloads[i].implementation = function() {
			console.warn("\n========================")
			console.warn("*** entered " + targetClassMethod);

			// print backtrace
			// Java.perform(function() {
			//	var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
			//	console.log("\nBacktrace:\n" + bt);
			// });

			// print args
			if (arguments.length) console.log();
			for (var j = 0; j < arguments.length; j++) {
				console.log("arg[" + j + "]: " + arguments[j] + "  type:" + typeof(arguments[j]));
			}

			// print retval
			var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?)
			console.log("\nretval: " + retval + "  type:" + typeof(retval));
			console.warn("\n*** exiting " + targetClassMethod);
			console.warn("========================\n")
			return retval;
		}
	}
}


// trace Module functions
function traceModule(impl, name)
{
	console.log("Tracing " + name);

	Interceptor.attach(impl, {

		onEnter: function(args) {

			// debug only the intended calls
			this.flag = false;
			// var filename = Memory.readCString(ptr(args[0]));
			// if (filename.indexOf("XYZ") === -1 && filename.indexOf("ZYX") === -1) // exclusion list
			// if (filename.indexOf("my.interesting.file") !== -1) // inclusion list
				this.flag = true;

			if (this.flag) {
				console.warn("\n========================")
				console.warn("*** entered " + name);

				// print backtrace
				console.log("\nBacktrace:\n" + Thread.backtrace(this.context, Backtracer.ACCURATE)
						.map(DebugSymbol.fromAddress).join("\n"));
			}
		},

		onLeave: function(retval) {

			if (this.flag) {
				// print retval
				console.log("\nretval: " + retval);
				console.warn("\n*** exiting " + name);
				console.warn("========================\n")
			}
		}

	});
}

// remove duplicates from array
function uniqBy(array, key)
{
        var seen = {};
        return array.filter(function(item) {
                var k = key(item);
                return seen.hasOwnProperty(k) ? false : (seen[k] = true);
        });
}

// usage examples
setTimeout(function() { // avoid java.lang.ClassNotFoundException

	Java.perform(function() {
		console.warn("-----------------------------------");
		console.warn("******first entering selector******");
		console.warn("-----------------------------------");
		trace("com.jussi.fridademo.MainActivity.getString");
		// trace("exports:*!open*");
		//trace("exports:*!write*");
		//trace("exports:*!malloc*");
		//trace("exports:*!free*");

	});
}, 0);



/**
在大佬的基础上进行的拓展
*/

/**
动态treace方法
 eg:
 	traceFunc("com.jussi.fridademo.MainActivity.getString")
*/
function traceFunc(funcName){
	Java.perform(function() {
		trace(funcName);
	});
}

/**
动态调用方法
 eg:
 	callFunc("com.jussi.fridademo.MainActivity.getString")
	callFunc("com.jussi.fridademo.MainActivity.getString",[1,2])
*/
function callFunc(funcName, args) { //定义导出函数

	var arr = funcName.split(".");
	var temp = new Array();
	for (var i = 0; i < arr.length-1; i++) {
		temp[i] = arr[i];
	}
	var className = temp.join(".");
	// console.log(className +"  "+ typeof(methodName));
	var methodName = arr[arr.length-1];
	// console.log(methodName +"  "+ typeof(methodName));

    Java.perform(function() {
        Java.choose(className, {
            onMatch: function (instance) {
            	var arg_string = [];
            	if(args){
            		console.log(args.length);
	            	if (args.length > 1) {
	            		arg_string = args.join(",");
	            	} else{
	            		arg_string = args;
	            	}
            	}      
                eval('instance.' + methodName + '('+ arg_string +')');
            },
            onComplete: function() {}
        });
    });
}

